//-----------------------------------------------------------------------------
// Sys Includes 
//-----------------------------------------------------------------------------
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <iostream>
#include <cstring>
#include <GL/glut.h>

//-----------------------------------------------------------------------------
// Bend2D Library Files 
//-----------------------------------------------------------------------------
#include "glutproject.h"
#include "point.h"
#include "wheeldata.h"
#include "windpoint.h"
#include "world.h"

//-----------------------------------------------------------------------------
// Local Include Files 
//-----------------------------------------------------------------------------
#include "myworld.h"
#include "myboundary.h"
#include "mywindpoint.h"
#include "myobjectcreator.h"
#include "wheelchair.h"

//-----------------------------------------------------------------------------
// Namespaces 
//-----------------------------------------------------------------------------
using namespace bend2d;
using namespace std;

//-----------------------------------------------------------------------------
// Macros 
//-----------------------------------------------------------------------------
#define trace(x) cout << x << "\n"
#define trace_error(x) cerr << "ERROR: " << x << "\n"


//-----------------------------------------------------------------------------
// Application 
//-----------------------------------------------------------------------------
class myGlutProject : public GlutProject
{
    private: 
        struct sockaddr_in m_Server;        
        int m_Sock;
        myWorld * myworld;
        WheelChair* wheelChair;

    public:
        myGlutProject() :
            m_Sock(-1)
        {
            bzero(&m_Server, sizeof(m_Server));
        }

        void CustomInit()
        {
            // Initializing Sockets //
            trace("Initializing Sockets...");
            const int serverport = 8100;
            m_Sock=socket(AF_INET, SOCK_DGRAM, 0);

            if (m_Sock < 0)
            { 
                trace_error("Opening socket");
                exit(-1);
            }

            m_Server.sin_family = AF_INET;
            m_Server.sin_addr.s_addr=INADDR_ANY;
            m_Server.sin_port=htons(serverport);

            if (bind(m_Sock,(struct sockaddr *)&m_Server,sizeof(m_Server)) < 0) 
            {
               trace_error("Binding");
               exit(-2);
            }

            // Initializing Bend2D World
            Factory::GetInstance()->RegistryObjectCreator(new myObjectCreator());

            trace("Creating world");
            Point worldBounds(640,480);
            myworld = new myWorld(worldBounds);
            
            wheelChair = new WheelChair(Point(320,240));
            myworld->RegistryObject(wheelChair);

            // Creating Bounds //
            myworld->RegistryObject(Factory::GetInstance()->AccessObjectCreator()->CreateBoundaryBox(Rectangle(20, 20, 600, 80)));
            myworld->RegistryObject(Factory::GetInstance()->AccessObjectCreator()->CreateBoundaryBox(Rectangle(20, 400, 600, 460)));
            myworld->RegistryObject(Factory::GetInstance()->AccessObjectCreator()->CreateBoundaryBox(Rectangle(80, 100, 140, 380)));
            myworld->RegistryObject(Factory::GetInstance()->AccessObjectCreator()->CreateBoundaryBox(Rectangle(480, 100, 540, 200)));
            myworld->RegistryObject(Factory::GetInstance()->AccessObjectCreator()->CreateBoundaryBox(Rectangle(480, 280, 540, 380)));
        }
        
        void ClearFunction()
        {
            glClearColor(1, 1, 1, 0);
            glClear(GL_COLOR_BUFFER_BIT);
        }

        void CustomRenderFunction()
        {
            wheelChair->CalculateWheelSpeed();
            myworld->Iterate(1);
            myworld->Iterated();
            myworld->Draw();


            /*Speed wheelChairSpeed = wheelChair->GetSpeed();
            wheelChairSpeed.SetAngle(wheelChairSpeed.GetAngle() + 0.001);
            wheelChair->SetSpeed(wheelChairSpeed);*/
        }

        void SetOrtho()
        {
            gluOrtho2D(-40, 680, 520, -40);
        }

        void ParseData(WheelData& data)
        {
            if(strcmp(data.m_Sender, "video0") == 0)
            {
                wheelChair->SetLeftWheelSpeed(data.m_Speed / 50.0);
            }
            if(strcmp(data.m_Sender, "video1") == 0)
            {
                wheelChair->SetRightWheelSpeed(data.m_Speed / -50.0);
            }
            data.m_Break = wheelChair->GetBreak();
        }

        void ReqData()
        {
            struct sockaddr_in from;
            int fromlen = sizeof(from);

            WheelData data;
            int n = recvfrom(m_Sock, &data, sizeof(WheelData), MSG_DONTWAIT, (struct sockaddr *)&from, (socklen_t*)&fromlen);

            if(n < 0)
            {
            }
            else if(n >= 0)
            {
                ParseData(data);
                sendto(m_Sock, &data, sizeof(WheelData), 0, (struct sockaddr *)&from, fromlen);
            }
        }

        void CustomIdleFunction()
        {
            GlutProject::RenderFunction();
            ReqData();
            wheelChair->Reset();
        }
};

int main(int argc, char* argv[])
{
    myGlutProject project;
    project.Init(argc, argv);
    project.MainLoop();
    return 0;
}
